package com.guli.order.service.impl;
/*
 * @Author 罗俊
 * @date 2020/11/18 - 1:18 下午
 *
 */

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guli.common.constant.OrderConstant;
import com.guli.common.constant.WareConstant;
import com.guli.common.pojo.vo.CartVo;
import com.guli.common.pojo.vo.SkuItemVo;
import com.guli.common.pojo.vo.StatusCodeEnum;
import com.guli.common.to.MemberTo;
import com.guli.common.to.StockWareTo;
import com.guli.common.utils.R;
//import com.guli.mall.serviceBase.globalException.MallException;
import com.guli.mall.serviceBase.globalException.MallException;
import com.guli.order.config.RabbitMqConfig;
import com.guli.order.entity.OrderEntity;
import com.guli.order.entity.OrderItemEntity;
import com.guli.order.feign.CartFeignClient;
import com.guli.order.feign.MemberFeignClient;
import com.guli.order.feign.ProductFeignClient;
import com.guli.order.feign.WareFeignClient;
import com.guli.order.interceptor.LoginUserInterceptor;
import com.guli.order.mapper.OrderMapper;
import com.guli.order.service.OrderItemService;
import com.guli.order.service.OrderService;
import com.guli.order.vo.*;
//import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import static com.guli.common.pojo.vo.StatusCodeEnum.TOKEN_INVALID;

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderEntity> implements OrderService {

    @Resource
    ExecutorService executorService;

    @Resource
    CartFeignClient cartFeignClient;

    @Resource
    MemberFeignClient memberFeignClient;

    @Resource
    WareFeignClient wareFeignClient;

    @Resource
    ProductFeignClient productFeignClient;

    @Resource
    OrderItemService orderItemService;

    @Resource
    RedisTemplate<String, String> redisTemplate;

    @Resource
    RabbitTemplate rabbitTemplate;


    private static final TypeReference<CartVo> CART_VO_TYPE_REFERENCE = new TypeReference<CartVo>() {
    };

    @Override
    public Page<OrderAndOrderItemsVo> queryPage(Integer currentPage, Integer size, Map<String, Object> params) {

        Page<OrderEntity> page = new Page<>(currentPage, size);

        QueryWrapper<OrderEntity> wrapper = new QueryWrapper<>();

        MemberTo memberTo = LoginUserInterceptor.THREAD_LOCAL.get();

        wrapper.eq("member_id", memberTo.getId());

        Page<OrderEntity> resultPage = this.page(page, wrapper);

        List<OrderAndOrderItemsVo> collect = resultPage.getRecords().parallelStream().map(order -> {

            OrderAndOrderItemsVo orderAndOrderItemsVo = new OrderAndOrderItemsVo();

            BeanUtils.copyProperties(order, orderAndOrderItemsVo);

            List<OrderItemEntity> itemsByOrderSn = orderItemService.getItemsByOrderSn(order.getOrderSn());

            orderAndOrderItemsVo.setOrderItemEntities(itemsByOrderSn);

            return orderAndOrderItemsVo;
        }).collect(Collectors.toList());

        Page<OrderAndOrderItemsVo> orderAndOrderItemsVoPage = new Page<>();

        BeanUtils.copyProperties(resultPage, orderAndOrderItemsVoPage);

        orderAndOrderItemsVoPage.setRecords(collect);

        return orderAndOrderItemsVoPage;
    }

    @Override
    public PreOrderInfoVo getPrePaymentInfo() throws ExecutionException, InterruptedException {

        MemberTo memberTo = LoginUserInterceptor.THREAD_LOCAL.get();

        // 1、手动保存当前请求的 requestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 查询本次订单所选中的购物项
        CompletableFuture<CartVo> queryOrderItemTask = CompletableFuture.supplyAsync(() -> {

            // 2、为新开的线程设置 requestAttributes
            RequestContextHolder.setRequestAttributes(requestAttributes);

            return getCartAndFilterUnSelectedItems();

        }, executorService);

        // 3、查询购物项的库存
        CompletableFuture<CartVo> queryItemStockTask = queryOrderItemTask.thenApplyAsync(cartVo -> {

            queryWareStockAndSetToItemsOfCart(cartVo);

            return cartVo;

        }, executorService);

        // 4、查询收货人以及收货地址
        CompletableFuture<List<MemberReceiveAddressVo>> queryReceiveAddressTask = CompletableFuture.supplyAsync(() -> {
            return memberFeignClient.getMemberReceiveAddressByMemberId(memberTo.getId());
        }, executorService);

        // 5、查询会员的积分信息（积分存储在会员表中）
        CompletableFuture<Integer> queryIntegrationTask = CompletableFuture.supplyAsync(() -> {
            R userInfo = memberFeignClient.getUserInfo(memberTo.getId());

            Map<String, String> member = (Map<String, String>) userInfo.get("member");

            String integration = member.get("integration");

            if (ObjectUtils.isEmpty(integration)) {
                return 0;
            }

            return Integer.parseInt(integration);

        }, executorService);

        // 6、将防重令牌 token 放入 redis 中
        CompletableFuture<String> writeTokenToRedisTask = CompletableFuture.supplyAsync(() -> {

            // set redis数据类型 不能单独为其中的元素设置过期时间，不好使
            //BoundSetOperations<String, String> setOps = redisTemplate.boundSetOps(OrderConstant.PRE_ORDER_TOKEN);

            String preOrderToken = UUID.randomUUID().toString();

            ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();

            opsForValue.set(getPreOrderTokenStr(memberTo), preOrderToken, 30, TimeUnit.MINUTES);

            return preOrderToken;
        }, executorService);


        CompletableFuture.allOf(queryItemStockTask, queryReceiveAddressTask, queryIntegrationTask, writeTokenToRedisTask);

        CartVo cartVo = queryItemStockTask.get();

        List<MemberReceiveAddressVo> memberReceiveAddressVos = queryReceiveAddressTask.get();

        Integer integration = queryIntegrationTask.get();

        String preOrderToken = writeTokenToRedisTask.get();

        // 合并到处查询到的结果
        PreOrderInfoVo preOrderInfoVo = new PreOrderInfoVo();

        preOrderInfoVo.setCartVo(cartVo);

        preOrderInfoVo.setMemberReceiveAddressVo(memberReceiveAddressVos);

        preOrderInfoVo.setIntegration(integration);

        preOrderInfoVo.setPreOrderToken(preOrderToken);


        // 7、合并收货人和购物车清单
        /*CompletableFuture<PreOrderInfoVo> preOrderInfoVoCompletableFuture = queryItemStockTask.thenCombine(queryReceiveAddressTask, (cart, receiveAddress) -> {
            PreOrderInfoVo preOrderInfoVo = new PreOrderInfoVo();
            preOrderInfoVo.setCartVo(cart);

            preOrderInfoVo.setMemberReceiveAddressVo(receiveAddress);

            return preOrderInfoVo;
        });*/


        // 8、设置积分
        /*CompletableFuture<PreOrderInfoVo> resultTask = preOrderInfoVoCompletableFuture.thenCombine(queryIntegrationTask, (preOrderInfoVo, integration) -> {

            preOrderInfoVo.setIntegration(integration);

            return preOrderInfoVo;
        });*/

        log.info("用户：【{}】要购买的商品是：【{}】", memberTo.getId(), preOrderInfoVo.getCartVo().getItems());

        return preOrderInfoVo;
    }


    private String getPreOrderTokenStr(MemberTo memberTo) {
        return OrderConstant.PRE_ORDER_TOKEN_PREFIX + memberTo.getId();
    }

    /**
     * 多线程下 还不能使用事务
     * 1、未使用事务
     *
     * @param orderSubmitVo
     * @return
     * @throws Throwable
     */
    @Transactional//(isolation = Isolation.REPEATABLE_READ)
    public String submitOrder2(OrderSubmitVo orderSubmitVo) throws Throwable {

        MemberTo memberTo = LoginUserInterceptor.THREAD_LOCAL.get();

        String preOrderTokenStr = getPreOrderTokenStr(memberTo);

       /*
        整个查询->比较->删除应该是原子的，不然不满足幂等性
        所以使用 lua 脚本来保证
       ValueOperations<String, String> forValue = redisTemplate.opsForValue();

       String preOrderToken = forValue.get(preOrderTokenStr);

        if(!orderSubmitVo.getPreOrderToken().equals(preOrderToken)){
            throw new MallException(TOKEN_INVALID);
        }

        redisTemplate.delete(preOrderTokenStr);*/

        // 1、结合了 查询->比较->删除（token防重令牌）的 lua脚本
        // 如果全部成功，则返回1，如果失败则返回0
        // 执行脚本操作时，记得将 redis 底层驱动从 lettuce-core 换成 jedis
        String queryCompareDeleteScriptStr = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

        RedisScript<Long> queryCompareDeleteScript = new DefaultRedisScript<>(queryCompareDeleteScriptStr, Long.class);

        Long execute = redisTemplate.execute(
                queryCompareDeleteScript,           // 执行的 lua 脚本
                Arrays.asList(preOrderTokenStr),    // 脚本中的 key
                orderSubmitVo.getPreOrderToken()    // 脚本中的 参数
        );

        log.info("提交的订单是：【{}】", orderSubmitVo);

        if (execute == 0) {
            throw new MallException(StatusCodeEnum.TOKEN_INVALID);
        }


        // 手动保存当前请求的 requestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 2、查询购物车中已经选择了的购物项
        CompletableFuture<CartVo> queryCartVo = CompletableFuture.supplyAsync(() -> {

            RequestContextHolder.setRequestAttributes(requestAttributes);

            CartVo cartAndFilterUnSelectedItems = getCartAndFilterUnSelectedItems();

            return cartAndFilterUnSelectedItems;
        }, executorService);

        // 3、为购物车添加spu信息（远程feign调用），并封装成订单项
        CompletableFuture<List<OrderItemEntity>> packagingToOrderItemTask = queryCartVo.thenApplyAsync(cartVo -> {

            return getCartAndPackagingToOrderObj(cartVo);

        }, executorService);

        // 4、验证用户添加运费后（修改收货地点）的前端计算出的价格，和后端计算出的价格是否一致
        CompletableFuture<OrderEntity> calculatePriceAndSetOrderTask = packagingToOrderItemTask.thenApplyAsync(orderItemEntities -> {

            // 创建订单
            OrderEntity orderEntity = new OrderEntity();

            // 设置会员id
            orderEntity.setMemberId(memberTo.getId());

            orderEntity.setMemberUsername(memberTo.getUsername());

            // 生成订单ID
            orderEntity.setOrderSn(IdWorker.getIdStr());

            verifyTotalPrice(orderItemEntities, orderSubmitVo, orderEntity);

            return orderEntity;

        }, executorService);

        // 5、保存订单
        CompletableFuture<OrderEntity> saveOrderTask = calculatePriceAndSetOrderTask.thenApplyAsync(orderEntity -> {

            this.save(orderEntity);

            return orderEntity;

        }, executorService);


        // 6、保存订单项
        CompletableFuture<Void> saveOrderItemTask = saveOrderTask.thenAcceptBothAsync(packagingToOrderItemTask, (orderEntity, orderItemEntities) -> {

            for (OrderItemEntity orderItemEntity : orderItemEntities) {

                orderItemEntity.setOrderId(orderEntity.getId());

                orderItemEntity.setOrderSn(orderEntity.getOrderSn());

                orderItemService.save(orderItemEntity);
            }

        }, executorService);


        // 7、锁定库存
        CompletableFuture<Void> lockWareStockTask = queryCartVo.thenAcceptAsync(cartVo -> {

            //TODO 问题：1、如果保存订单、订单项出异常，要反向解锁库存
            //TODO      2、锁定库存正常、但因为网络原因，这边收到的是超时异常，还要撤回锁库存的操作
            StockWareTo stockWareTo = new StockWareTo();

            stockWareTo.setItemVos((List<SkuItemVo>) cartVo.getItems());

            R r = wareFeignClient.lockWareStock(stockWareTo);

            if (r.getCode() != 0) {
                throw new MallException(r.getCode(), r.getMsg());
            }

        }, executorService);


        try {
            saveOrderItemTask.get();
            lockWareStockTask.get();
        } catch (Exception e) {
            throw e.getCause() instanceof MallException ? (MallException) e.getCause() : e.getCause();
        }

        return saveOrderTask.get().getOrderSn();

    }

    /**
     * 2、使用 seata 的 二阶段提交（AT）控制事务，性能过低
     *
     * @param orderSubmitVo
     * @return
     * @throws Throwable
     */
//    @GlobalTransactional //TODO 高并发场景下不可使用二阶段事务(AT)，因为是全局锁，会让并发任务串行化，性能低
    @Transactional//(isolation = Isolation.REPEATABLE_READ)
    //@Override
    public String submitOrder3(OrderSubmitVo orderSubmitVo) throws Throwable {

        MemberTo memberTo = LoginUserInterceptor.THREAD_LOCAL.get();

        String preOrderTokenStr = getPreOrderTokenStr(memberTo);

       /*
        整个查询->比较->删除应该是原子的，不然不满足幂等性
        所以使用 lua 脚本来保证
       ValueOperations<String, String> forValue = redisTemplate.opsForValue();

       String preOrderToken = forValue.get(preOrderTokenStr);

        if(!orderSubmitVo.getPreOrderToken().equals(preOrderToken)){
            throw new MallException(TOKEN_INVALID);
        }

        redisTemplate.delete(preOrderTokenStr);*/

        // 1、结合了 查询->比较->删除（token防重令牌）的 lua脚本
        // 如果全部成功，则返回1，如果失败则返回0
        // 执行脚本操作时，记得将 redis 底层驱动从 lettuce-core 换成 jedis
        String queryCompareDeleteScriptStr = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

        RedisScript<Long> queryCompareDeleteScript = new DefaultRedisScript<>(queryCompareDeleteScriptStr, Long.class);

        Long execute = redisTemplate.execute(
                queryCompareDeleteScript,           // 执行的 lua 脚本
                Arrays.asList(preOrderTokenStr),    // 脚本中的 key
                orderSubmitVo.getPreOrderToken()    // 脚本中的 参数
        );

        log.info("提交的订单是：【{}】", orderSubmitVo);

        if (execute == 0) {
            throw new MallException(StatusCodeEnum.TOKEN_INVALID);
        }


        // 手动保存当前请求的 requestAttributes
        //RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 2、查询购物车中已经选择了的购物项
        //RequestContextHolder.setRequestAttributes(requestAttributes);

        CartVo cartVo = getCartAndFilterUnSelectedItems();


        // 3、为购物车添加spu信息（远程feign调用），并封装成订单项

        List<OrderItemEntity> orderItemEntities = getCartAndPackagingToOrderObj(cartVo);


        // 4、验证用户添加运费后（修改收货地点）的前端计算出的价格，和后端计算出的价格是否一致

        // 创建订单
        OrderEntity orderEntity = new OrderEntity();

        // 设置会员id
        orderEntity.setMemberId(memberTo.getId());

        orderEntity.setMemberUsername(memberTo.getUsername());

        // 生成订单ID
        orderEntity.setOrderSn(IdWorker.getIdStr());

        verifyTotalPrice(orderItemEntities, orderSubmitVo, orderEntity);

        // 5、保存订单

        this.save(orderEntity);

        // 6、保存订单项

        for (OrderItemEntity orderItemEntity : orderItemEntities) {

            orderItemEntity.setOrderId(orderEntity.getId());

            orderItemEntity.setOrderSn(orderEntity.getOrderSn());

            orderItemService.save(orderItemEntity);
        }

        // 7、锁定库存

        //TODO 问题：1、如果保存订单、订单项出异常，要反向解锁库存
        //TODO      2、锁定库存正常、但因为网络原因，这边收到的是超时异常，还要撤回锁库存的操作
        StockWareTo stockWareTo = new StockWareTo();

        stockWareTo.setItemVos((List<SkuItemVo>) cartVo.getItems());

        R r = wareFeignClient.lockWareStock(stockWareTo);

        if (r.getCode() != 0) {
            throw new MallException(r.getCode(), r.getMsg());
        }

        int i = 10 / 0;


        return orderEntity.getOrderSn();

    }

    //    @GlobalTransactional
    @Transactional//(isolation = Isolation.REPEATABLE_READ)
    @Override
    public String submitOrder(OrderSubmitVo orderSubmitVo) throws Throwable {

        MemberTo memberTo = LoginUserInterceptor.THREAD_LOCAL.get();

        String preOrderTokenStr = getPreOrderTokenStr(memberTo);

       /*
        整个查询->比较->删除应该是原子的，不然不满足幂等性
        所以使用 lua 脚本来保证
       ValueOperations<String, String> forValue = redisTemplate.opsForValue();

       String preOrderToken = forValue.get(preOrderTokenStr);

        if(!orderSubmitVo.getPreOrderToken().equals(preOrderToken)){
            throw new MallException(TOKEN_INVALID);
        }

        redisTemplate.delete(preOrderTokenStr);*/

        // 1、结合了 查询->比较->删除（token防重令牌）的 lua脚本
        // 如果全部成功，则返回1，如果失败则返回0
        // 执行脚本操作时，记得将 redis 底层驱动从 lettuce-core 换成 jedis
        String queryCompareDeleteScriptStr = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

        RedisScript<Long> queryCompareDeleteScript = new DefaultRedisScript<>(queryCompareDeleteScriptStr, Long.class);

        Long execute = redisTemplate.execute(
                queryCompareDeleteScript,           // 执行的 lua 脚本
                Arrays.asList(preOrderTokenStr),    // 脚本中的 key
                orderSubmitVo.getPreOrderToken()    // 脚本中的 参数
        );

        log.info("提交的订单是：【{}】", orderSubmitVo);

        if (execute == 0) {
            throw new MallException(StatusCodeEnum.TOKEN_INVALID);
        }


        // 手动保存当前请求的 requestAttributes
        //RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 2、查询购物车中已经选择了的购物项
        //RequestContextHolder.setRequestAttributes(requestAttributes);

        CartVo cartVo = getCartAndFilterUnSelectedItems();


        // 3、为购物车添加spu信息（远程feign调用），并封装成订单项

        List<OrderItemEntity> orderItemEntities = getCartAndPackagingToOrderObj(cartVo);


        // 创建订单
        OrderEntity orderEntity = new OrderEntity();

        // 设置会员id
        orderEntity.setMemberId(memberTo.getId());

        orderEntity.setMemberUsername(memberTo.getUsername());

        // 生成订单ID
        orderEntity.setOrderSn(IdWorker.getIdStr());


        // 4、验证用户添加运费后（修改收货地点）的前端计算出的价格，和后端计算出的价格是否一致
        verifyTotalPrice(orderItemEntities, orderSubmitVo, orderEntity);

        // 5、保存订单

        this.save(orderEntity);

        // 6、保存订单项

        for (OrderItemEntity orderItemEntity : orderItemEntities) {

            orderItemEntity.setOrderId(orderEntity.getId());

            orderItemEntity.setOrderSn(orderEntity.getOrderSn());

            orderItemService.save(orderItemEntity);
        }

        return lockStockByMQ((List<SkuItemVo>) cartVo.getItems(), orderEntity);

    }

    private String lockStockByMQ(List<SkuItemVo> skuItemVos, OrderEntity orderEntity) {
        // 7、远程锁定库存

        // 问题：1、如果保存订单、订单项出异常，要反向解锁库存
        //      2、锁定库存正常、但因为网络原因，这边收到的是超时异常，还要撤回锁库存的操作
        // 已解决
        StockWareTo stockWareTo = new StockWareTo();

        stockWareTo.setItemVos(skuItemVos);

        stockWareTo.setOrderId(orderEntity.getId());

        stockWareTo.setOrderSn(orderEntity.getOrderSn());

        // 为了保证高并发，如果出错时，发消息给消息队列，库存服务订阅消息，库存自己来回滚
        // 库存服务可以使用自动解锁模式（自动解除库存）
        R r = wareFeignClient.lockWareStock(stockWareTo);

        // 发送锁定库存的消息到消息队列（测试）
//        amqpTemplate.convertAndSend(RabbitMqConfig.ORDER_EVENT_EXCHANGE,RabbitMqConfig.ORDER_CREATE_ORDER_ROUTING_KEY,stockWareTo);

        if (r.getCode() != 0) {
            throw new MallException(r.getCode(), r.getMsg());
        }

//        int i = 10 / 0;
        // 订单超时自动解锁
        CompletableFuture.runAsync(() -> {
            try {
                //TODO 保证消息一定要发送出去，每一个消息都可以做好日志记录（给数据库保存每一个消息的详细信息）
                //TODO 定期扫描数据库，将发送失败的消息再发送一遍
                rabbitTemplate.convertAndSend(OrderConstant.ORDER_EVENT_EXCHANGE, OrderConstant.ORDER_CREATE_ORDER_ROUTING_KEY, orderEntity);

            } catch (Exception e) {
                //TODO 将没有发送成功的消息，进行重试发送
            }
        }, executorService);


        return orderEntity.getOrderSn();
    }

    @Override
    public OrderEntity getOrderByOrderSn(String orderSn) {

        QueryWrapper<OrderEntity> wrapper = new QueryWrapper<>();

        wrapper.eq("order_sn", orderSn);

        Long memberId = LoginUserInterceptor.THREAD_LOCAL.get().getId();

        wrapper.eq("member_id", memberId);

        wrapper.eq("status", OrderConstant.Status.WAITING_FOR_PAYMENT);

        return this.getOne(wrapper);

    }

    @Override
    public boolean updatePaymentStatus(String orderSn) {
        // 根据支付宝返回的订单跟踪id，获取订单
        QueryWrapper<OrderEntity> wrapper = new QueryWrapper<>();

        wrapper.eq("order_sn", orderSn);

        wrapper.eq("status", OrderConstant.Status.WAITING_FOR_PAYMENT.getCode());

        OrderEntity orderEntity = new OrderEntity();

        orderEntity.setStatus(OrderConstant.Status.WAITING_FOR_DELIVER);

        orderEntity.setPayType(OrderConstant.PayType.ZHI_FU_BAO);

        boolean update = this.update(orderEntity, wrapper);

        if (update) {
            log.info("已将 orderSn = {}的订单状态更新为{}", orderSn, OrderConstant.Status.WAITING_FOR_DELIVER.getMessage());

            //TODO 下单成功，执行更具订单id扣减库存
            rabbitTemplate.convertAndSend(WareConstant.STOCK_EVENT_EXCHANGE, WareConstant.STOCK_DEDUCTION_ROUTING_KEY, orderSn);

            return true;
        } else {
            log.warn("没有变更 orderSn = {} 的订单状态（已做等幂性处理）", orderSn);
        }

        return false;
    }

    @Override
    @Transactional
    public void submitOrder(String orderSn, Long memberId, Long spuId, Long skuId, Integer count, BigDecimal price) {

        OrderEntity orderEntity = new OrderEntity();

        orderEntity.setOrderSn(orderSn);

        orderEntity.setMemberId(memberId);

        orderEntity.setPayAmount(price);

        orderEntity.setStatus(OrderConstant.Status.WAITING_FOR_PAYMENT);

        this.save(orderEntity);

        SkuItemVo skuItemVo = new SkuItemVo();

        skuItemVo.setCount(count);

        skuItemVo.setPrice(price);

        skuItemVo.setSpuId(spuId);

        skuItemVo.setSkuId(skuId);

        OrderItemEntity orderItemEntity = new OrderItemEntity();

        orderItemEntity.setOrderSn(orderSn);

        orderItemEntity.setSpuId(spuId);

        orderItemEntity.setSkuId(skuId);

        orderItemService.save(orderItemEntity);

        this.lockStockByMQ(Collections.singletonList(skuItemVo), orderEntity);

    }

    private static final TypeReference<MemberReceiveAddressVo> MEMBER_RECEIVE_ADDRESS_VO_TYPE_REFERENCE = new TypeReference<MemberReceiveAddressVo>() {
    };

    /**
     * 验证用户添加运费后（修改收货地点）的前端计算出的价格，和后端计算出的价格是否一致
     *
     * @param orderItemEntities 后端通过查询购物车得到的订单项
     * @param orderSubmitVo     前端提交的购物车信息
     * @param orderEntity       订单对象
     * @return
     */
    private void verifyTotalPrice(List<OrderItemEntity> orderItemEntities, OrderSubmitVo orderSubmitVo, OrderEntity orderEntity) {

        // 获取运费信息
        R r = memberFeignClient.calculateCarriage(orderSubmitVo.getMemberReceiveAddressId());

        MemberReceiveAddressVo memberReceiveAddressVo = JSON.parseObject(JSON.toJSONString(r.get("data")), MEMBER_RECEIVE_ADDRESS_VO_TYPE_REFERENCE);

        BigDecimal carriage = memberReceiveAddressVo.getCarriage();

        orderEntity.setReceiverName(memberReceiveAddressVo.getName());

        orderEntity.setReceiverPhone(memberReceiveAddressVo.getPhone());

        orderEntity.setReceiverProvince(memberReceiveAddressVo.getProvince());

        orderEntity.setReceiverCity(memberReceiveAddressVo.getCity());

        orderEntity.setReceiverRegion(memberReceiveAddressVo.getRegion());

        orderEntity.setReceiverDetailAddress(memberReceiveAddressVo.getDetailAddress());


        // 所有订单项的总额
        BigDecimal orderItemPriceSum = new BigDecimal(0);

        // 所有商品促销分解金额
        BigDecimal promotionAmountSum = new BigDecimal(0);

        // 所有优惠券优惠分解金额
        BigDecimal couponAmountSum = new BigDecimal(0);

        // 所有积分优惠分解金额
        BigDecimal integrationAmountSum = new BigDecimal(0);

        // 所有赠送积分
        Integer giftIntegrationSum = 0;

        // 所有赠送成长值
        Integer giftGrowthSum = 0;


        for (OrderItemEntity orderItemEntity : orderItemEntities) {

            orderItemPriceSum = orderItemPriceSum.add(orderItemEntity.getRealAmount());

            promotionAmountSum = promotionAmountSum.add(orderItemEntity.getPromotionAmount());

            couponAmountSum = couponAmountSum.add(orderItemEntity.getCouponAmount());

            integrationAmountSum = integrationAmountSum.add(orderItemEntity.getIntegrationAmount());

            giftIntegrationSum = giftIntegrationSum + orderItemEntity.getGiftIntegration();

            giftGrowthSum = giftGrowthSum + orderItemEntity.getGiftGrowth();
        }

        BigDecimal resultByJava = orderItemPriceSum.add(carriage);


        // 设置订单总额
        orderEntity.setTotalAmount(orderItemPriceSum);

        // 设置运费
        orderEntity.setFreightAmount(carriage);

        // 设置应付总额
        orderEntity.setPayAmount(resultByJava);

        // 设置订单总的商品促销分解金额
        orderEntity.setPromotionAmount(promotionAmountSum);

        // 设置订单总的优惠券优惠分解金额
        orderEntity.setCouponAmount(couponAmountSum);

        // 设置订单总的积分优惠分解金额
        orderEntity.setIntegrationAmount(integrationAmountSum);

        // 设置订单总的可以获得的成长值
        orderEntity.setGrowth(giftGrowthSum);

        // 设置订单总的可以获得的积分
        orderEntity.setIntegration(giftIntegrationSum);

        // 设置自动收货时间（天）
        orderEntity.setAutoConfirmDay(OrderConstant.DEFAULT_ORDER_AUTO_CONFIRM_TIME_DAY);

        // 设置订单的删除状态
        orderEntity.setDeleteStatus(OrderConstant.DeleteStatus.UNDELETED);

        // 设置订单的状态
        orderEntity.setStatus(OrderConstant.Status.WAITING_FOR_PAYMENT);

        /**
         * 由于后端进行了精确计算，所以可能会有偏差，偏差小于 0.01 是可以接受的
         * */
        boolean isEquals = Math.abs(resultByJava.subtract(orderSubmitVo.getTotalAmount()).doubleValue()) < 0.01;

        log.info("前端提交的价格是：{}，后端计算的价格是：{}，是否相等：{}", orderSubmitVo.getTotalAmount(), resultByJava, isEquals);

        if (!isEquals) {
            throw new MallException(StatusCodeEnum.TOTAL_AMOUNT_NOT_EQUALS);
        }
    }

    /**
     * 为购物车添加spu信息（远程feign调用），并封装成订单项
     *
     * @return
     */
    private List<OrderItemEntity> getCartAndPackagingToOrderObj(CartVo cartVo) {

        List<OrderItemEntity> orderItemEntities = cartVo.getItems().parallelStream().map(item -> {

            OrderItemEntity orderItemEntity = new OrderItemEntity();


            // 1、设置订单的 spu 信息

            R spuInfoR = productFeignClient.getSpuInfoBySpuId(item.getSpuId());

            Map<String, Object> spuInfoMap = (Map<String, Object>) spuInfoR.get("spuInfo");

            orderItemEntity.setSpuName(spuInfoMap.get("spuName").toString());

            orderItemEntity.setSpuId(Long.parseLong(spuInfoMap.get("id").toString()));

            orderItemEntity.setSpuBrand(spuInfoMap.get("brandId").toString());

            orderItemEntity.setSpuPic(spuInfoMap.get("spuDescription").toString());

            orderItemEntity.setCategoryId(Long.parseLong(spuInfoMap.get("catalogId").toString()));


            // 2、设置订单的 Sku 信息
            orderItemEntity.setSkuId(item.getSkuId());

            orderItemEntity.setSkuName(item.getSkuName());

            orderItemEntity.setSkuPrice(item.getPrice());

            orderItemEntity.setSkuQuantity(item.getCount());

            orderItemEntity.setSkuPic(item.getSkuDefaultImg());

            orderItemEntity.setSkuAttrsVals(StringUtils.collectionToDelimitedString(item.getSpuAttr(), ","));

            // 3、订单的优惠信息（不做）

            //TODO 4、积分信息（不做）
            orderItemEntity.setGiftIntegration(0);

            orderItemEntity.setGiftGrowth(0);

            //TODO 5、订单的优惠卷价格
            orderItemEntity.setPromotionAmount(new BigDecimal(0));

            orderItemEntity.setCouponAmount(new BigDecimal(0));

            orderItemEntity.setIntegrationAmount(new BigDecimal(0));

            // 6、单个订单项的的总价格(实际金额)
            BigDecimal origin = item.getPrice().multiply(new BigDecimal(item.getCount()));

            BigDecimal finalPrice = origin.subtract(orderItemEntity.getPromotionAmount()).subtract(orderItemEntity.getCouponAmount()).subtract(orderItemEntity.getIntegrationAmount());

            orderItemEntity.setRealAmount(finalPrice);


            return orderItemEntity;
        }).collect(Collectors.toList());

        return orderItemEntities;
    }

    /**
     * 查询本次订单所选中的购物项
     *
     * @return
     */
    private CartVo getCartAndFilterUnSelectedItems() {

        R cartList = cartFeignClient.getCartList();

        CartVo cart = JSON.parseObject(JSON.toJSONString(cartList.get("cart")), CART_VO_TYPE_REFERENCE);

        List<? extends SkuItemVo> collect = cart.getItems().stream().filter(item -> {
            return item.getIsSelected();
        }).collect(Collectors.toList());

        cart.setItems(collect);

        return cart;
    }

    /**
     * 查询购物车中商品的库存，并为商品设置库存
     *
     * @param cartVo
     */
    private void queryWareStockAndSetToItemsOfCart(CartVo cartVo) {

        List<Long> skuIds = cartVo.getItems().stream().map(item -> {
            return item.getSkuId();
        }).collect(Collectors.toList());

        Map<Long, Integer> stockBySkuIdArray = wareFeignClient.getStockBySkuIdArray(skuIds);

        List<SkuItemVoWithStockNumber> collect = cartVo.getItems().stream().map(item -> {
            SkuItemVoWithStockNumber stockNumber = new SkuItemVoWithStockNumber();

            BeanUtils.copyProperties(item, stockNumber);

            stockNumber.setStock(stockBySkuIdArray.get(item.getSkuId()));

            return stockNumber;

        }).collect(Collectors.toList());

        cartVo.setItems(collect);
    }
}
